iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
自我挑戰組

web 應用開發筆記系列 第 17

[Day 17] 資料視覺化實戰篇 - 互動式設計

  • 分享至 

  • xImage
  •  

繼上一篇完成熱門標籤趨勢圖後,我們可以進一步進行資料視覺化的一項重要功能:互動式介面,透過互動式的介面可以讓使用者更進一步的探索資料,如何設計互動式的功能及效果也是十分重要的。

為了更近一步看出趨勢的變化,我們可以設計將每個時間軸相同的標籤相連,以此來觀察時間趨勢的變化,另一個重要的互動式介面則是顯示功能,如何有效地告訴使用者詳細的資訊可以更大程度的瞭解資料。

result

實作

準備資料

[
  {
    "tagId": 3,
    "tagName": "情人",
    "lineData": [
      {
        "time": "2017/03/26",
        "rank": 3
      },
      {
        "time": "2017/04/02",
        "rank": 3
      },
      {
        "time": "2017/04/09",
        "rank": 3
      },
      ...
    ]
  }, 
  ...
]

將資料組成以上結構,lineData 表示的是每個標所有的時間資料

將圓圈相連

let lineFunction = d3.line()
  .x(function(d) { 
  	return x(new Date(d.time)); 
  })
  .y(function(d) { 
  	return y(d.rank); 
  })

function drawLine( tagLineArray ){
	// The line SVG Path we draw
	// 每一條線代表一個標籤的 lifespan 出現點
	for( let i=0; i< tagLineArray.length; i++){
		bubbles.data([tagLineArray[i].lineData])
	      .append("path")
	      .attr("class", "line"+ "tag"+tagLineArray[i].tagId)
	      .attr("stroke","none")
	      .style("fill", "none")
	      .style("stroke-width", "2px")
	      .attr("d", lineFunction);
	}
}

lineFunction - 將每一個 path 的位置 mapping 到實際的資料值域上

更改作畫順序

如此一來就可以將相同的標籤圓圈連線,但此時會遇到一個小問題,由於 SVG 疊加在畫布上的時候會有先後順序的問題,因此我們需要將 SVG 的疊加順序改成已選取的在最上層,如此才不會有已選取的標籤卻被其他的包千遮住的問題。

// changing back and front
// https://github.com/wbkd/d3-extended
d3.selection.prototype.moveToFront = function() {  
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};
d3.selection.prototype.moveToBack = function() {  
    return this.each(function() { 
        var firstChild = this.parentNode.firstChild; 
        if (firstChild) { 
            this.parentNode.insertBefore(this, firstChild); 
        } 
    });
};

設計 tooltip

為了讓使用者知道每一個圓圈的標籤內容,可在滑鼠移到圓圈時顯示一個帶有標籤資訊的小視窗,此視窗則稱為 tooltip。

var tool_tip = d3.tip()         //tooltip to show the box of the message
      .attr("class", "tips")
      .offset([-10, 0]);

增加互動方法

function biingDataAndDrawingBubble( data, based_term, svgElement ){
	var color_based_array = [];		//mean or total_pv
	var article_counts_array = [];

	//取得總pv數的序數陣列為的是取得十分位數的切點
	//依據文章的排序將之作為大小正規化的切點
	data.sort(function(a,b) { return +a.article_counts - +b.article_counts; });
	for( var i=0; i<data.length; i++ ){
		data[i].countsId = i;
		article_counts_array.push(data[i].article_counts);
		if(based_term == "pv")
			color_based_array.push(data[i].total_pv);
		else if(based_term == "mean")
			color_based_array.push(data[i].mean);
	}
	//大小的正規化方法
	var sizeScale = d3.scaleQuantile()
					  .domain([0, data.length/2, data.length])
					  .range([5, 10, 20]);
	//顏色的十分位數正歸化方法
	var colorQuantile = d3.scaleQuantile()
						.domain(color_based_array)
						.range([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]);

	//將十個數值對應到顏色的數值裡
	var colorScaleByQuantile = d3.scaleSequential()
	            				 .domain([0, 100]);

	(based_term == "pv") ? colorScaleByQuantile.interpolator(d3.interpolatePlasma) : colorScaleByQuantile.interpolator(d3.interpolateViridis);


	svgElement.selectAll("circle")
			.data(data)
			.enter()
			.append("circle")
			.attr("class", function(d){
				return "tag" + d.tagID;
			})
			.attr("cx", function(d){
				var t = x(new Date(d.weekStart));
				return t-4;
			})
			.attr("cy", function(d){
				var yPosition;

				(based_term == "pv") ? yPosition = y(d.total_pv_rank) : yPosition = y(d.meanRank);

				return yPosition;
			})
			.attr("r", function(d){
				var size = sizeScale(d.countsId);
				return size;
			})
			.attr("fill-opacity","0.6")
			.attr("stroke","black")
			.attr("stroke-width",0)
			.attr("fill", function(d){
				var quantileValue;

				(based_term == "pv") ? quantileValue = colorQuantile(d.total_pv) : quantileValue = colorQuantile(d.mean);

				return colorScaleByQuantile(quantileValue);
			})
			.on('mouseover', function(d){
				tool_tip.show(d);
				var s = ".tag" + d.tagID;
				var l = ".linetag" + d.tagID;

				d3.selectAll(s).moveToFront();

				d3.selectAll(s).attr("fill-opacity","1.0")
				d3.selectAll(s).attr("stroke-width",2);
				d3.selectAll(l).attr("stroke", "brown");

			})
			.on('mouseout', function(d){
				tool_tip.hide(d);
				var s = ".tag" + d.tagID;
				var l = ".linetag" + d.tagID;
				var quantileValue;

				(based_term == "pv") ? quantileValue = colorQuantile(d.total_pv) : quantileValue = colorQuantile(d.mean);

				d3.selectAll(s).moveToBack();

				d3.selectAll(l).attr("stroke", "none");
				d3.selectAll(s).attr("stroke-width",0);
				d3.selectAll(s).attr("fill-opacity","0.6")
			});

	//==================set tooltip==========================
	(based_term == "pv") ? tool_tip.html(function(d) {
      	return "Tag: "+d.tagName+"</br>文章篇數: "+d.article_counts+"</br>總pv: "+addingComma(d.total_pv)+"</br>";
   	  }) : tool_tip.html(function(d) {
      	return "Tag: "+d.tagName+"</br>文章篇數: "+d.article_counts+"</br>平均pv: "+addingComma(Math.round(d.mean*100)/100)+"</br>";
   	  })
}

mouseover - 當滑鼠移到圓圈上時顯示 tooltip 並顯示線條以及將所有元素移到最上面
mouseout - 讓線條消失以及將元素恢復原來的位置


上一篇
[Day 16] 資料視覺化實戰篇 - 初探 D3 下集
下一篇
[Day 18] 資料視覺化實戰篇 - 資料分析
系列文
web 應用開發筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言